{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS -fno-warn-name-shadowing #-}

module Hpaste.Model.Irclogs where

import           Hpaste.Types

import           Control.Applicative
import           Control.Arrow
import           Control.Monad.IO
import           Control.Monad.Reader
import           Data.ByteString          (ByteString)
import qualified Data.ByteString          as S
import           Data.Char
import           Data.Either
import           Data.List                (find)
import           Data.List.Utils
import           Data.Maybe
import           Data.Monoid.Operator     ((++))
import           Data.Text                (Text)
import qualified Data.Text                as T
import           Data.Text.Encoding
import           Data.Text.Encoding.Error (lenientDecode)
import           Data.Time
import           Network.Curl.Download
import           Prelude                  hiding ((++))
import           System.Directory
import           System.FilePath
import           System.Locale

-- | Get IRC logs for the given channel narrowed down to the given date/time.
getNarrowedLogs :: String -- ^ Channel name.
                -> String -- ^ Date.
                -> String -- ^ Time.
                -> Controller (Either String [Text])
getNarrowedLogs channel year time = do
  case parseIrcDate year of
    Nothing -> return $ Left $ "Unable to parse year: " ++ year
    Just date -> do
      days <- mapM (getLogs channel . showIrcDate) [addDays (-1) date,date,addDays 1 date]
      let events = concat (rights days)
      return (Right (fromMaybe events
                               (narrowBy (T.isPrefixOf datetime) events <|>
                                narrowBy (T.isPrefixOf dateminute) events <|>
                                narrowBy (T.isPrefixOf datehour) events <|>
                                narrowBy (T.isPrefixOf datestr) events <|>
                                narrowBy (T.isPrefixOf dateday) events)))
  
  where narrowBy pred events =
          case find pred (filter crap events) of
            Nothing -> Nothing
            Just _res -> Just $ narrow count pred (filter crap events)
        count = 50
        datetime   = T.pack $ year ++ "-" ++ replace "-" ":" time
        dateminute = T.pack $ year ++ "-" ++ replace "-" ":" (reverse . drop 2 . reverse $ time)
        datehour   = T.pack $ year ++ "-" ++ replace "-" ":" (reverse . drop 5 . reverse $ time)
        datestr    = T.pack $ year ++ "-"
        dateday    = T.pack $ reverse . drop 2 . reverse $ year
        crap = not . T.isPrefixOf " --- " . T.dropWhile (not . isSpace)

-- | Narrow to surrounding predicate.
narrow :: Int -> (a -> Bool) -> [a] -> [a]
narrow n f = uncurry (++) . (reverse . take n . reverse *** take n) . break f

-- | Get IRC logs for the given channel and date.
getLogs :: String -- ^ Channel name.
        -> String -- ^ Date.
        -> Controller (Either String [Text])
getLogs channel year = do
  dir <- asks $ configIrcDir . controllerStateConfig
  io $ do
    now <- fmap (showIrcDate . utctDay) getCurrentTime
    result <- openURICached (year == now) (file dir) uri
    case result of
      Left err    -> return $ Left $ uri ++ ": " ++ err
      Right bytes -> return $ Right (map addYear (T.lines (decodeUtf8With lenientDecode bytes)))

  where uri = "http://tunes.org/~nef/logs/" ++ channel ++ "/" ++ yearStr
        file dir = dir </> channel ++ "-" ++ yearStr
        yearStr = replace "-" "." (drop 2 year)
        addYear line = T.pack year ++ "-" ++ line

-- | Open the URI and cache the result.
openURICached :: Bool -> FilePath -> String -> IO (Either String ByteString)
openURICached noCache path url = do
  exists <- doesFileExist path
  if exists && not noCache
     then fmap Right $ S.readFile path
     else do result <- openURI url
             case result of
               Right bytes -> S.writeFile path bytes
               _           -> return ()
             return result

-- | Parse an IRC date string into a date.
parseIrcDate :: String -> Maybe Day
parseIrcDate = parseTime defaultTimeLocale "%Y-%m-%d"

-- | Show a date to an IRC date format.
showIrcDate :: Day -> String
showIrcDate = formatTime defaultTimeLocale "%Y-%m-%d"

-- | Show a date to an IRC date format.
showIrcDateTime :: UTCTime -> String
showIrcDateTime =
  formatTime defaultTimeLocale "%Y-%m-%d/%H-%M-%S" . addUTCTime ((40*60)+((-9)*60*60))
